﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Xml.Linq;
using Common;

namespace DO.Workflows
{
    [DataContract]
    public class WorkflowDesign
    {
        #region props
        [DataMember]
        public Guid WorkflowID { get; set; }
        [DataMember]
        public string WorkflowName { get; set; }
        [DataMember]
        public string FileName { get; set; }
        [DataMember]
        public Version Version { get; set; }
        [DataMember]
        public string Author { get; set; }
        [DataMember]
        public DateTime CreatedOn { get; set; }
        [DataMember]
        public DateTime ModifiedOn { get; set; }
        [DataMember]
        public Dictionary<Guid, WorkflowComponentBase> Components { get; set; }
        /// <summary>
        /// upstream component ID --> list of downstream component IDs
        /// </summary>
        [DataMember]
        public Dictionary<Guid, List<Guid>> SuccessConnectors { get; set; }
        /// <summary>
        /// upstream component ID --> list of downstream component IDs
        /// </summary>
        [DataMember]
        public Dictionary<Guid, List<Guid>> FailureConnectors { get; set; }
        /// <summary>
        /// FromComponentID -> ToComponentID -> List of CodeFieldMapper
        /// </summary>
        [DataMember]
        public Dictionary<Guid, Dictionary<Guid, List<CodeFieldMapper>>> DataPipeConverters { get; set; }
        [DataMember]
        public List<GlobalVariable> Vars { get; set; }
        [DataMember]
        public string FailureNotifyEmailAddress { get; set; }
        #endregion

        #region trigger
        //public WorkflowTrigger Trigger
        //{
        //    get
        //    {
        //        int triggerCount = 0;
        //        WorkflowTrigger trigger = null;
        //        foreach(WorkflowComponentBase component in this.Components.Values)
        //        {
        //            if(component.GetType().IsSubclassOf(typeof(WorkflowTrigger)))
        //            {
        //                trigger = (WorkflowTrigger) component;
        //                triggerCount++;
        //            }
        //        }
        //        if(triggerCount==1)
        //            return trigger;
        //        else if(triggerCount>1)
        //            throw new Exception("Workflow can only have one trigger");
        //        else
        //            return null;
        //    }
        //}
        #endregion

        public WorkflowDesign()
        {
            this.WorkflowID = Guid.NewGuid();
            this.WorkflowName = "New workflow";
            this.FileName = "New workflow.wox";
            this.Version = new Version(1, 0, 0, 0);
            this.CreatedOn = DateTime.Now;
            this.ModifiedOn = this.CreatedOn;
            this.Author = string.Empty;
            this.Components = new Dictionary<Guid, WorkflowComponentBase>();
            this.SuccessConnectors = new Dictionary<Guid, List<Guid>>();
            this.FailureConnectors = new Dictionary<Guid, List<Guid>>();
            this.DataPipeConverters=new Dictionary<Guid, Dictionary<Guid, List<CodeFieldMapper>>>();
            this.Vars=new List<GlobalVariable>();
            this.FailureNotifyEmailAddress = string.Empty;
        }

        public WorkflowDesign(string author)
            : this()
        {
            this.Author = author;
        }

        public XDocument Save()
        {
            this.ModifiedOn = DateTime.Now;
            XElement xml = new XElement(
                "Workflow",
                new XAttribute("WorkflowID", this.WorkflowID.ToString()),
                new XAttribute("WorkflowName", this.WorkflowName),
                new XAttribute("FileName", this.FileName),
                new XAttribute("Version", this.Version.ToString()),
                new XAttribute("Author", this.Author),
                new XAttribute("CreatedOn", this.CreatedOn.ToString()),
                new XAttribute("ModifiedOn", this.ModifiedOn.ToString()),
                new XAttribute("FailureNotifyEmailAddress", this.FailureNotifyEmailAddress));
            // nodes
            XElement componentRootElement = new XElement("Components");
            foreach (WorkflowComponentBase component in this.Components.Values)
            {
                XElement xe = component.Save();
                componentRootElement.Add(xe);
            }
            xml.Add(componentRootElement);

            // connectors
            XElement connectorRootElement = new XElement("Connectors");
            foreach (Guid fromID in this.SuccessConnectors.Keys)
            {
                List<Guid> toIDs = this.SuccessConnectors[fromID];
                foreach (Guid toID in toIDs)
                {
                    XElement xe = new XElement("Connector",
                        new XAttribute("FromComponentID", fromID.ToString()),
                        new XAttribute("ToComponentID", toID.ToString()),
                        new XAttribute("FromPortType", PortType.SuccessOutput));
                    connectorRootElement.Add(xe);
                }
            }
            foreach (Guid fromID in this.FailureConnectors.Keys)
            {
                List<Guid> toIDs = this.FailureConnectors[fromID];
                foreach (Guid toID in toIDs)
                {
                    XElement xe = new XElement(
                        "Connector",
                        new XAttribute("FromComponentID", fromID.ToString()),
                        new XAttribute("ToComponentID", toID.ToString()),
                        new XAttribute("FromPortType", PortType.ErrorOutput));
                    connectorRootElement.Add(xe);
                }
            }
            xml.Add(connectorRootElement);

            // field mappers 
            XElement mapperRootElement=new XElement("FieldMappers");
            foreach(Guid fromComponentID in this.DataPipeConverters.Keys)
            {
                XElement fromComponentElement=new XElement("FromComponent",
                    new XAttribute("ComponentID",fromComponentID.ToString()));
                mapperRootElement.Add(fromComponentElement);
                foreach (Guid toComponentID in this.DataPipeConverters[fromComponentID].Keys)
                {
                    XElement toComponentElement=new XElement("ToComponent",
                        new XAttribute("ComponentID", toComponentID.ToString()));
                    fromComponentElement.Add(toComponentElement);
                    foreach(CodeFieldMapper fieldMapper in this.DataPipeConverters[fromComponentID][toComponentID])
                    {
                        XElement mapperElement=new XElement("FieldMapper",
                            new XAttribute("FromFieldName",fieldMapper.FromFieldName),
                            new XAttribute("ToFieldName",fieldMapper.ToFieldName),
                            new XAttribute("FromType",fieldMapper.FromType.FullName),
                            new XAttribute("ToType",fieldMapper.ToType.FullName),
                            new XAttribute("Code",fieldMapper.Code));
                        toComponentElement.Add(mapperElement);
                    }
                }
            }
            xml.Add(mapperRootElement);

            // global variables
            XElement varsRootElement=new XElement("Variables");
            foreach(GlobalVariable var in this.Vars)
            {
                XElement varXE = new XElement("Variable",
                    new XAttribute("Name", var.VarName),
                    new XAttribute("DataType", var.DataType.ToString()),
                    new XAttribute("Value", var.VarValue));
                varsRootElement.Add(varXE);
            }
            xml.Add(varsRootElement);

            XDocument xDoc = new XDocument(
                new XDeclaration("1.0", "utf-8", "yes"),
                new XComment("Workflow design created by Xiaodong"),
                xml);
            return xDoc;
        }

        public static WorkflowDesign Read(XDocument xDoc)
        {
            WorkflowDesign wfDesign = new WorkflowDesign();
            XElement root = xDoc.Element("Workflow");
            wfDesign.WorkflowID = new Guid(root.Attribute("WorkflowID").Value);
            wfDesign.WorkflowName = root.Attribute("WorkflowName").Value;
            wfDesign.FileName = root.Attribute("FileName").Value;
            wfDesign.Version = new Version(root.Attribute("Version").Value);
            wfDesign.Author = root.Attribute("Author").Value;
            wfDesign.CreatedOn = DateTime.Parse(root.Attribute("CreatedOn").Value);
            wfDesign.ModifiedOn = DateTime.Parse(root.Attribute("ModifiedOn").Value);
            wfDesign.FailureNotifyEmailAddress = root.Attribute("FailureNotifyEmailAddress").Value;

            foreach (XElement xe in root.Elements("Components").Elements("Component"))
            {
                string typeName = xe.Attribute("Type").Value;
                if (typeName.LastIndexOf(".") > 0)
                    typeName = typeName.Substring(typeName.LastIndexOf(".") + 1);
                if (ComponentsRegister.ComponentTypes != null && ComponentsRegister.ComponentTypes.ContainsKey(typeName))
                {
                    Type type = ComponentsRegister.ComponentTypes[typeName];
                    WorkflowComponentBase component = (WorkflowComponentBase) Activator.CreateInstance(type);
                    component = component.Instantiate(xe);
                    wfDesign.Components.Add(component.ComponentID, component);
                }
            }
            
            foreach (XElement xe in root.Elements("Connectors").Elements("Connector"))
            {
                Guid fromID = new Guid(xe.Attribute("FromComponentID").Value);
                Guid toID = new Guid(xe.Attribute("ToComponentID").Value);
                PortType portType = (PortType)Enum.Parse(typeof(PortType), xe.Attribute("FromPortType").Value, false);
                if (portType == PortType.SuccessOutput)
                {
                    if (wfDesign.SuccessConnectors.ContainsKey(fromID))
                    {
                        List<Guid> toIDs = wfDesign.SuccessConnectors[fromID];
                        toIDs.Add(toID);
                        wfDesign.SuccessConnectors[fromID] = toIDs;
                    }
                    else
                    {
                        List<Guid> toIDs = new List<Guid>();
                        toIDs.Add(toID);
                        wfDesign.SuccessConnectors.Add(fromID, toIDs);
                    }
                }
                else
                {
                    if (wfDesign.FailureConnectors.ContainsKey(fromID))
                    {
                        List<Guid> toIDs = wfDesign.FailureConnectors[fromID];
                        toIDs.Add(toID);
                        wfDesign.FailureConnectors[fromID] = toIDs;
                    }
                    else
                    {
                        List<Guid> toIDs = new List<Guid>();
                        toIDs.Add(toID);
                        wfDesign.FailureConnectors.Add(fromID, toIDs);
                    }
                }
            }
            
            wfDesign.DataPipeConverters=new Dictionary<Guid, Dictionary<Guid, List<CodeFieldMapper>>>();
            foreach(XElement fromComponentElement in root.Elements("FromComponent"))
            {
                Guid fromComponentID=new Guid(fromComponentElement.Attribute("ComponentID").Value);
                Dictionary<Guid,List<CodeFieldMapper>> toFieldMappers=new Dictionary<Guid, List<CodeFieldMapper>>();
                foreach(XElement toComponentElement in fromComponentElement.Elements("ToComponent"))
                {
                    Guid toComponentID=new Guid(toComponentElement.Attribute("ComponentID").Value);
                    List<CodeFieldMapper> fieldMappers=new List<CodeFieldMapper>();
                    foreach(XElement mapperElement in toComponentElement.Elements("FieldMapper"))
                    {
                        CodeFieldMapper fieldMapper=
                            new CodeFieldMapper
                                {
                                    FromComponentID=fromComponentID,
                                    ToComponentID=toComponentID,
                                    FromFieldName=mapperElement.Attribute("FromFieldName").Value,
                                    ToFieldName=mapperElement.Attribute("ToFieldName").Value,
                                    FromType=Type.GetType(mapperElement.Attribute("FromType").Value),
                                    ToType=Type.GetType(mapperElement.Attribute("ToType").Value),
                                    Code=mapperElement.Attribute("Code").Value
                                };
                        fieldMappers.Add(fieldMapper);
                    }
                    toFieldMappers.Add(toComponentID, fieldMappers);
                }
                wfDesign.DataPipeConverters.Add(fromComponentID, toFieldMappers);
            }

            wfDesign.Vars=new List<GlobalVariable>();
            foreach(XElement varElement in root.Elements("Variables").Elements("Variable"))
            {
                string varName = varElement.Attribute("Name").Value;
                SimpleDataType dataType =
                    (SimpleDataType)Enum.Parse(typeof(SimpleDataType), varElement.Attribute("DataType").Value);
                string varValue = varElement.Attribute("Value").Value;
                GlobalVariable var = new GlobalVariable(varName, dataType, varValue);
                wfDesign.Vars.Add(var);
            }

            return wfDesign;
        }

        public string XmlData
        {
            get
            {
                XDocument xDoc = this.Save();
                return xDoc.ToString();
            }
        }

        public static WorkflowDesign Read(string xmlData)
        {
            XDocument xDoc = XDocument.Parse(xmlData);
            return WorkflowDesign.Read(xDoc);
        }
    }
}
